// Ofri Keidar 302933981 group 05

/**
 * This class implements Model interface. The class manages system's database- queries, creating objects, writing data to database file and loading objects from file
 * when program starts to run 
 */

package model;

import java.io.*;
import java.util.Map;
import java.util.HashMap;
import java.util.List;
import java.util.LinkedList;

import model.apartment.*;
import model.building.Building;

public class DataBase implements Model {
	
	// members
	private static final int UPDATE_BOUND = 5; // number of data updates until database file is updated
	private static final String FILE_NAME = "database.txt"; // name of database file
	private List<Building> buildingsList; // list of all buildings in system
	private static int updateCounter; // counts how many data updates were done
	
	// private methods
	
	/**
	 * Scan buildings list and save each building to database file.
	 * If finished updating with no IOException raised, data has been updated successfully. 
	 * Otherwise, an error happened and data has not been saved.
	 * @return- True if update has been successful, False if not  
	 */
	private boolean updateDatabase() {
		
		// try writing all objects to database file
		try {
		
			FileOutputStream stream = new FileOutputStream(FILE_NAME); // create new stream to database file
			ObjectOutputStream writer = new ObjectOutputStream(stream); // create new object writer
			
			// scan buildings list and write each building to database file
			for (Building currBuilding : buildingsList) {
				// write current building to database
				 writer.writeObject(currBuilding);
			}
			
			// done scanning, close object writer and file 
			writer.close();
			stream.close();
			// data has been updated successfully
			return true;
			
		} catch (IOException e) {
			// error raised during update
			return false;
		}
	}
	
	/**
	 * Returns map of all apartments in system of given type- key is apartment and value 
	 * is String containing apartment number in its building and its building address
	 * @param type- String of class name to look for
	 * @return map of apartments of given type
	 */
	private Map<Apartment, String> getApartments(String type) {
		
		// initialize hash map to hold apartments
		Map<Apartment, String> apartmentMap = new HashMap<Apartment, String>();
		
		// scan all buildings and get their apartments of given type
		for (Building currBuilding : buildingsList) {			
			int apartmentIndx = 1; // index for apartment in each building			
			for (Apartment currApartment : currBuilding.getApartments()) {
				
				// check if current apartment of current building is of wanted type
				if ( (currApartment.getClass().getSimpleName()).equals(type) ) {
					
					// compose value string
					String valString = "Address: " + currBuilding.getBuildingNumber() + " " + currBuilding.getStreetName(); // compose complete address
					valString = valString + ", Apartment #: " + apartmentIndx;
					
					// add current apartment to list
					apartmentMap.put(currApartment, valString);
				}				
				++apartmentIndx; // increment index
			}
		}
		
		// return the apartments found
		return apartmentMap;
	}
	
	// public methods
	
	/**
	 * Constructor- loads objects from database file to memory.
	 * If no file with FILE_NAME exists, a file with this name is created and buildings list 
	 * is initialized as an empty list
	 * @throws IOException- thrown if cannot handle files
	 * @throws ClassNotFoundException- thrown if wrong class in file
	 */
	public DataBase() throws IOException, ClassNotFoundException {

		// initialize data update counter
		updateCounter = 0;
		
		// allocate a new Buildings list
		buildingsList = new LinkedList<Building>();
		
		// load buildings saved in file to buildings list
		try {
			
			// open database file
			FileInputStream stream = new FileInputStream(FILE_NAME);
			ObjectInputStream reader = new ObjectInputStream(stream);
			
			// scan file to its end
			while (true) {
				
				// try reading objects from database
				try {
					buildingsList.add((Building)reader.readObject());
					
				} catch (EOFException e) {
					// no more objects, stop scanning
					break;
				}
			}
			
			// done loading buildings, close database file
			reader.close();
			stream.close();
			
		} catch (IOException e) {
			// no such file			
			FileOutputStream stream = new FileOutputStream(FILE_NAME); // create new database file
			stream.close(); // close file
		}
	}
	
	/**
	 * Gets list of type for new building's apartments, list of maps of apartments' parameters and new building's address.
	 * Creates new apartments with given details and a building at given address holding those apartments
	 */
	@Override
	public void addNewBuilding(LinkedList<String> apartmentTypes, LinkedList< Map<String, String> > parameterList, String stName, int bldNumber) {
		
		
		// get number of new building's apartments
		int numOfAparments = parameterList.size(); 
		// allocate apartment array for the new building
		Apartment[] apartments = new Apartment[numOfAparments];
		// use an apartment factory to create all apartments
		ApartmentFactory factory = new ApartmentFactory(); 
		
		// create apartments according to given details
		for (int i = 0; i < numOfAparments; ++i) {
			apartments[i] = factory.create(apartmentTypes.get(i), parameterList.get(i));
		}
		
		// create a new building with these apartments at given address
		Building newBuilding = new Building(stName, bldNumber, apartments);
		
		// add the new building to buildings list
		buildingsList.add(newBuilding);
		
		// increment update counter
		++updateCounter;

		// check if reached maximal number of non-saved data updates
		if (updateCounter == UPDATE_BOUND) {
			// initialize update counter
			updateCounter = 0;
			// update database
			updateDatabase();
		}
	}

	
	/**
	 * Uses getApartments methods to find all flats in system.
	 * Returns map of all flats in database- key is flat and value is String
	 * containing flat number in its building and its building address  
	 * @return
	 */
	@SuppressWarnings({ "rawtypes", "unchecked" })
	@Override
	public Map<Flat, String> getAllFlats() {
		// get all flats and return them
		return (Map)getApartments("Flat");	
	}
	
	/**
	 * Uses getApartments methods to find all garden apartments in system.
	 * Returns map of all garden apartments in database- key is garden apartment and value is String
	 * containing garden apartment's number in its building and its building address
	 * @return
	 */
	@SuppressWarnings({ "rawtypes", "unchecked" })
	@Override
	public Map<GardenApartment, String> getAllGardenAprtmnts() {
		// get all garden apartments and return them
		return (Map)getApartments("GardenApartment");		
	}

	/**
	 * Uses getApartments methods to find all penthouses in system.
	 * Returns map of all penthouses in database- key is penthouse and value is String
	 * containing penthouse's number in its building and its building address
	 * @return
	 */
	@SuppressWarnings({ "rawtypes", "unchecked" })
	@Override
	public Map<Penthouse, String> getAllPenthouses() {
		// get all penthouses and return them
		return (Map)getApartments("Penthouse");
	}

	/**
	 * Scans each building's apartments and finds all vacant apartments.
	 * Once done scanning, returns map of apartment found- key is apartment and value is
	 * String containing apartment's number in its building and its building address
	 * @return
	 */
	@Override
	public Map<Apartment, String> getVacantApartments() {
		
		// initialize hash map of all vacant apartments
		Map<Apartment, String> vacantApartments = new HashMap<Apartment, String>();
		
		// scan all buildings and get their vacant apartments
		for (Building currBuilding : buildingsList) {			
			int apartmentIndx = 1; // index for apartment in each building
			// scan current building's apartments
			for (Apartment currApartment : currBuilding.getApartments()) {
								
				// check if current apartment of current building is vacant
				if ( currApartment.isVacant() ) {
					
					// compose value string
					String valString = "Address: " + currBuilding.getBuildingNumber() + " " + currBuilding.getStreetName(); // compose complete address
					valString = valString + ", Apartment #: " + apartmentIndx; // add apartment's number 
					
					// add current apartment to list
					vacantApartments.put(currApartment, valString);
				}				
				++apartmentIndx; // increment index
			}
		}
		
		// return map of vacant apartments
		return vacantApartments;
	}
	
	/**
	 * Scans building list and searches building with street name and number.
	 * If such building is found, the building is returned. Otherwise, null is returned
	 * @param stName- name of building's street
	 * @param bldNumber- number of building on street
	 * @return building at given address or null if there is not a building at given address
	 */
	@Override
	public Building getBuilding(String stName, int bldNumber) {
		
		// scan building list
		for (Building currBuilding : buildingsList) {
			
			// check current building's address
			if (currBuilding.getStreetName().equals(stName) && currBuilding.getBuildingNumber() == bldNumber) {
				// found building at given address
				return currBuilding;
			}
		}
		
		// there is no building at given address
		return null;
	}
	
	/**
	 * Prepares system to shut down by updating data
	 * @return- True if shut down successfully, False if not  
	 */
	@Override
	public boolean shutDownSystem() {
		// update new data before shutting down
		return updateDatabase();
	}
	
	
}
